package com.tools.web.http;

import com.fasterxml.jackson.core.type.TypeReference;
import com.tools.common.container.MapEnum;
import com.tools.common.container.OwnPairs;
import com.tools.common.container.OwnRows;
import com.tools.common.container.SetEnum;
import com.tools.common.object.C;
import com.tools.common.object.json.JsonKit;
import com.tools.common.object.Note;
import com.tools.common.object.ObjKit;
import com.tools.common.object.string.VerifyKit;
import org.apache.http.client.config.RequestConfig;

import java.net.URL;
import java.util.*;

/**
 * HTTP 客户端发送请求
 * 所需的参数配置项实体
 * 该类是作为父类对唯一子类
 * 做数据解耦用的
 * */
@Note("HTTP 客户端发送请求所需的参数配置项实体。" +
        "该类是作为父类对唯一子类做数据解耦用的")
class HttpBuildEntity {

    @Note("请求 URL")
    String url;

    @Note("请求参数")
    Map<String, Object> parameters;

    @Note("请求头")
    Map<String, String> headers;

    @Note("请求配置信息")
    RequestConfig requestConfig;

    @Note("响应结果包装对象")
    HttpResult httpResult;

    @Note("上传的文件集合，注意该集合是以 UploadFile 的 key 属性作为文件是否重复的判断依据")
    Map<String, UploadFile> uploadFiles;

    @Note("是否开启异步，默认关闭")
    boolean async = false;

    @Note("是否在发送请求时，用日志对象打印详细的请求响应信息")
    boolean printLog = false;

    @Note("异步请求的回调，若开启了异步，这个必须要初始化")
    HttpAsyncCallback asyncCallback;

    /* **************************************************************************************
     *
     *          构造器
     *
     * **************************************************************************************
     * */

    HttpBuildEntity(String url) {
        if(url == null || url.isEmpty()) throw new NullPointerException("构造 HTTP 请求，必须需要 URL 参数不为空");
        this.url = url;
    }

    HttpBuildEntity(URL url) {
        if(url == null) throw new NullPointerException("构造 HTTP 请求，必须需要 URL 参数不为空");
        this.url = url.toString();
    }

    /* **************************************************************************************
     *
     *          Getter / Setter
     *
     * **************************************************************************************
     * */

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        if(url == null || url.isEmpty()) throw new NullPointerException("不可设置空的 URL");
        this.url = url;
    }

    public void setUrl(URL url) {
        if(url == null) throw new NullPointerException("不可设置空的 URL");
        this.url = url.toString();
    }

    public Map<String, Object> getParameters() {
        return parameters;
    }

    public void setParameters(Map parameters) {
        this.parameters = (parameters == null || parameters.isEmpty())
                ? null
                : ObjKit.toHashMap(parameters, new C<String>() {}, new C<Object>() {});
    }

    public void setParameters(OwnPairs parameters) {
        this.parameters = (parameters == null || parameters.isEmpty())
                ? null
                : ObjKit.toHashMap(parameters, new C<String>() {}, new C<Object>() {});
    }

    public void setParameters(Object javaBean) {
        if(javaBean == null) return;
        this.parameters = ObjKit.toHashMap(javaBean);
    }

    public void setParameters(String jsonObject) {
        if(VerifyKit.isJsonObject(jsonObject)) {
            this.parameters = JsonKit.toEntity(jsonObject, new TypeReference<Map<String, Object>>() {});
        }
    }

    public Map<String, String> getHeaders() {
        return headers;
    }

    public void setHeaders(Map headers) {
        if(headers == null || headers.isEmpty()) return;
        C<String> c = new C<String>() {};
        this.headers = ObjKit.toHashMap(headers, c, c);
    }

    public void setHeaders(OwnPairs headers) {
        if(headers == null || headers.isEmpty()) return;
        C<String> c = new C<String>() {};
        this.headers = ObjKit.toHashMap(headers, c, c);
    }

    public RequestConfig getRequestConfig() {
        return requestConfig;
    }

    public void setRequestConfig(RequestConfig requestConfig) {
        this.requestConfig = requestConfig;
    }

    public HttpResult getHttpResult() {
        return httpResult;
    }

    public void setHttpResult(HttpResult httpResult) {
        if(httpResult == null) throw new NullPointerException("HTTP 请求的结果包装对象不能为 null");
        this.httpResult = httpResult;
    }


    public Set<UploadFile> getUploadFiles() {
        return (uploadFiles == null || uploadFiles.isEmpty())
                ? new HashSet<>()
                : SetEnum.HASH.newSet(uploadFiles.values());
    }

    public void setUploadFiles(UploadFile... uploadFiles) {
        if(uploadFiles != null && uploadFiles.length != 0) {
            this.uploadFiles = MapEnum.HASH.newMap(uploadFiles.length);
            private_filePuts(uploadFiles);
        }
    }

    public void setUploadFiles(Collection<UploadFile> uploadFiles) {
        if(uploadFiles == null || uploadFiles.isEmpty()) return;
        this.uploadFiles = MapEnum.HASH.newMap(uploadFiles.size());
        private_filePuts(uploadFiles);
    }

    public void setUploadFiles(OwnRows<UploadFile> uploadFiles) {
        if(uploadFiles == null || uploadFiles.isEmpty()) return;
        this.uploadFiles = MapEnum.HASH.newMap(uploadFiles.size());
        int index = 0;
        private_filePuts(uploadFiles);
    }

    public boolean isAsync() {
        return async;
    }

    public void setAsync(boolean async) {
        this.async = async;
    }

    public HttpAsyncCallback getAsyncCallback() {
        private_checkAsyncCallback(asyncCallback);
        return asyncCallback;
    }

    public void setAsyncCallback(HttpAsyncCallback callback) {
        private_checkAsyncCallback(callback);
        this.asyncCallback = callback;
    }

    public boolean isPrintLog() {
        return printLog;
    }

    public void setPrintLog(boolean printLog) {
        this.printLog = printLog;
    }




    /* **************************************************************************************
     *
     *          参数操作的链式方法
     *
     * **************************************************************************************
     * */

    void protected_param(String name, Object value) {
        private_lazyInitParameterMap(1);
        this.parameters.put(name, value);
    }

    void protected_params(Map parameters) {
        if(parameters == null || parameters.isEmpty()) return;
        if(this.parameters == null) {
            this.setParameters(parameters);
            return;
        }
        private_paramPuts(parameters.entrySet());
    }


    void protected_params(OwnPairs parameters) {
        if(parameters == null || parameters.isEmpty()) return;
        if(this.parameters != null) {
            private_paramPuts(parameters.entrySet());
            return;
        }
        this.setParameters(parameters);
    }

    void protected_params(Object javaBean) {
        if(javaBean == null) return;
        if(this.parameters == null) {
            this.setParameters(javaBean);
            return;
        }
        Map<String, Object> valuesMap = ObjKit.allFieldValue(javaBean);
        for (Map.Entry<String, Object> e : valuesMap.entrySet()) {
            Object fieldValue = e.getValue();
            if(fieldValue == null) continue;
            this.parameters.put(e.getKey(), fieldValue);
        }
    }


    void protected_params(String jsonObject) {
        if(VerifyKit.isJsonObject(jsonObject)) {
            if(this.parameters == null) {
                this.setParameters(jsonObject);
                return;
            }
            HashMap<String, Object> map = JsonKit.toEntity(jsonObject, new TypeReference<HashMap<String, Object>>() {});
            this.parameters.putAll(map);
        }
    }


    public Object getParam(String name) {
        if(parameters == null || parameters.isEmpty()) return null;
        return parameters.get(name);
    }

    public <T>T getParam(String name, Class<T> valueClass) {
        if(parameters == null || parameters.isEmpty()) return null;
        return (T) parameters.get(name);
    }

    public <T>T getParam(String name, C<T> valueType) {
        if(parameters == null || parameters.isEmpty()) return null;
        return (T) ObjKit.toRealValue(parameters.get(name), valueType);
    }

    public int paramsSize() {
        return (parameters == null) ? 0 : parameters.size();
    }

    void protected_clearParams() {
        if(parameters == null || parameters.isEmpty()) return;
        this.parameters.clear();
    }

    void protected_removeParam(String name) {
        if(parameters == null) return;
        this.parameters.remove(name);
    }

    void protected_removeParams(String... names) {
        if(parameters == null || parameters.isEmpty() || names == null || names.length == 0) return;
        for(String name : names) parameters.remove(name);
    }

    void protected_removeParams(Iterable<String> names) {
        if(parameters == null || parameters.isEmpty() || names == null) return;
        for(String name : names) parameters.remove(name);
    }

    public boolean containsParam(String name) {
        if(parameters == null || parameters.isEmpty()) return false;
        return parameters.containsKey(name);
    }

    public boolean containsAllParam(String[] names) {
        if(parameters == null || parameters.isEmpty()) return false;
        for (String n : names) if(!parameters.containsKey(n)) return false;
        return true;
    }

    public boolean containsAllParam(Iterable<String> names) {
        if(parameters == null || parameters.isEmpty()) return false;
        for (String n : names) if(!parameters.containsKey(n)) return false;
        return true;
    }

    public boolean containsAnyParam(String... names) {
        if(parameters == null || parameters.isEmpty()) return false;
        for (String n : names) if(parameters.containsKey(n)) return true;
        return false;
    }

    public boolean containsAnyParam(Iterable<String> names) {
        if(parameters == null || parameters.isEmpty()) return false;
        for (String n : names) if(parameters.containsKey(n)) return true;
        return false;
    }

    /* **************************************************************************************
     *
     *          请求头操作的链式方法
     *
     * **************************************************************************************
     * */

    void protected_header(String name, String value) {
        if(headers == null) this.headers = MapEnum.HASH.newMap(1);
        this.headers.put(name, value);
    }

    void protected_headers(Map headers) {
        if(headers == null || headers.isEmpty()) return;
        if(this.headers == null) {
            this.setHeaders(headers);
            return;
        }
        private_headerPuts(headers.entrySet());
    }


    void protected_headers(OwnPairs headers) {
        if(headers == null || headers.isEmpty()) return;
        if(this.headers != null) {
            private_headerPuts(headers.entrySet());
            return;
        }
        this.setHeaders(headers);
    }


    public String getHeader(String name) {
        if(headers == null || headers.isEmpty()) return null;
        return headers.get(name);
    }

    public int headersSize() {
        return (headers == null) ? 0 : headers.size();
    }

    void protected_clearHeaders() {
        if(headers == null || headers.isEmpty()) return;
        this.headers.clear();
    }

    void protected_removeHeader(String name) {
        if(headers == null || headers.isEmpty()) return;
        this.headers.remove(name);
    }

    void protected_removeHeaders(String[] names) {
        if(headers == null || headers.isEmpty() || names == null || names.length == 0) return;
        for(String name : names) headers.remove(name);
    }

    void protected_removeHeaders(Iterable<String> names) {
        if(headers == null || headers.isEmpty() || names == null) return;
        for(String name : names) headers.remove(name);
    }

    public boolean containsHeader(String name) {
        if(headers == null || headers.isEmpty()) return false;
        return headers.containsKey(name);
    }

    public boolean containsAllHeader(String... names) {
        if(headers == null || headers.isEmpty()) return false;
        for (String n : names) if(!headers.containsKey(n)) return false;
        return true;
    }

    public boolean containsAllHeader(Iterable<String> names) {
        if(headers == null || headers.isEmpty()) return false;
        for (String n : names) if(!headers.containsKey(n)) return false;
        return true;
    }

    public boolean containsAnyHeader(String... names) {
        if(headers == null || headers.isEmpty()) return false;
        for (String n : names) if(headers.containsKey(n)) return true;
        return false;
    }

    public boolean containsAnyHeader(Iterable<String> names) {
        if(headers == null || headers.isEmpty()) return false;
        for (String n : names) if(headers.containsKey(n)) return true;
        return false;
    }

    /* **************************************************************************************
     *
     *          上传文件操作的链式方法
     *
     * **************************************************************************************
     * */

    public void protected_addFile(UploadFile uploadFile) {
        if(uploadFiles == null) this.uploadFiles = MapEnum.HASH.newMap(1);
        this.uploadFiles.put(uploadFile.getUploadKey(), uploadFile);
    }

    public void protected_addFiles(UploadFile[] uploadFiles) {
        if(uploadFiles == null || uploadFiles.length == 0) return;
        if(this.uploadFiles == null) {
            this.setUploadFiles(uploadFiles);
            return;
        }
        private_filePuts(uploadFiles);
    }

    public void protected_addFiles(Collection<UploadFile> uploadFiles) {
        if(uploadFiles == null || uploadFiles.isEmpty()) return;
        if(this.uploadFiles == null) {
            this.setUploadFiles(uploadFiles);
            return;
        }
        private_filePuts(uploadFiles);
    }

    public void protected_addFiles(OwnRows<UploadFile> uploadFiles) {
        if(uploadFiles == null || uploadFiles.isEmpty()) return;
        if(this.uploadFiles == null) {
            this.setUploadFiles(uploadFiles);
            return;
        }
        private_filePuts(uploadFiles);
    }

    public void protected_removeFile(String uploadKey) {
        if(uploadFiles == null || uploadFiles.isEmpty()) return;
        this.uploadFiles.remove(uploadKey);
    }

    public void protected_removeFiles(String... uploadKeys) {
        if(uploadKeys == null || uploadKeys.length == 0) return;
        if(uploadFiles == null || uploadFiles.isEmpty()) return;
        for (String key : uploadKeys) this.uploadFiles.remove(key);
    }

    public void protected_removeFiles(Iterable<String> uploadKeys) {
        if(uploadKeys == null) return;
        if(uploadFiles == null || uploadFiles.isEmpty()) return;
        for (String key : uploadKeys) this.uploadFiles.remove(key);
    }


    public UploadFile getUploadFile(String uploadKey) {
        return (uploadFiles == null || uploadFiles.isEmpty())
                ? null
                : uploadFiles.get(uploadKey);
    }

    public int uploadFileSize() {
        return (uploadFiles == null) ? 0 : uploadFiles.size();
    }

    public boolean containsUploadFile(String uploadKey) {
        return uploadFiles != null && !uploadFiles.isEmpty() && uploadFiles.containsKey(uploadKey);
    }

    public boolean containsAllUploadFile(String... uploadKeys) {
        if(uploadKeys == null || uploadKeys.length == 0) return false;
        if(uploadFiles == null || uploadFiles.isEmpty()) return false;
        for (String key : uploadKeys) if(!uploadFiles.containsKey(key)) return false;
        return true;
    }

    public boolean containsAllUploadFile(Iterable<String> uploadKeys) {
        if(uploadKeys == null) return false;
        if(uploadFiles == null || uploadFiles.isEmpty()) return false;
        for (String key : uploadKeys) if(!uploadFiles.containsKey(key)) return false;
        return true;
    }

    public boolean containsAnyUploadFile(String... uploadKeys) {
        if(uploadKeys == null || uploadKeys.length == 0) return false;
        if(uploadFiles == null || uploadFiles.isEmpty()) return false;
        for (String key : uploadKeys) if(uploadFiles.containsKey(key)) return true;
        return false;
    }

    public boolean containsAnyUploadFile(Iterable<String> uploadKeys) {
        if(uploadKeys == null) return false;
        if(uploadFiles == null || uploadFiles.isEmpty()) return false;
        for (String key : uploadKeys) if(uploadFiles.containsKey(key)) return true;
        return false;
    }

    /* **************************************************************************************
     *
     *          私有逻辑
     *
     * **************************************************************************************
     * */

    @Note("开启异步后，其回调属性不能为 null")
    private void private_checkAsyncCallback(HttpAsyncCallback callback) {
        if(async && callback == null) {
            throw new IllegalArgumentException("当开启异步请求（设置 async 属性为 true 时），其回调方法不能设置为 null");
        }
    }

    @Note("（非线程安全）懒加载 RequestConfig.Builder 对象，用于链式设置 RequestConfig 的各项参数")
    private void private_lazyInitParameterMap(int initSize) {
        if(parameters != null) return;
        this.parameters = MapEnum.HASH.newMap(initSize);
    }


    @Note("遍历 Set<Map.Entry> 集合，将里面的键值对 put 进参数 Map 集合里")
    private void private_paramPuts(Set entrySet) {
        for (Object o : entrySet) {
            Map.Entry e = (Map.Entry) o;
            Object key = e.getKey();
            String name = (key == null) ? "null" : key.toString();
            this.parameters.put(name, e.getValue());
        }
    }

    @Note("遍历 Set<Map.Entry> 集合，将里面的键值对 put 进参数 Map 集合里")
    private void private_headerPuts(Set entrySet) {
        for (Object o : entrySet) {
            Map.Entry e = (Map.Entry) o;
            Object key = e.getKey();
            Object value = e.getValue();
            String name = (key == null) ? "null" : key.toString();
            String header = (value == null) ? "" : value.toString();
            this.headers.put(name, header);
        }
    }


    @Note("遍历上传文件数组，将里面的文件 key 和文件实例一一加入 Map 集合里")
    private void private_filePuts(UploadFile[] uploadFiles) {
        for (int i = 0; i < uploadFiles.length; i++) {
            UploadFile file = uploadFiles[i];
            if(file == null) throw new NullPointerException("第 " + i + " 个位置待上传的文件为空");
            this.uploadFiles.put(file.getUploadKey(), file);
        }
    }

    @Note("遍历上传文件数组，将里面的文件 key 和文件实例一一加入 Map 集合里")
    private void private_filePuts(Iterable<UploadFile> uploadFiles) {
        int index = 0;
        for (UploadFile file : uploadFiles) {
            if(file == null) throw new NullPointerException("第 " + index + " 个位置待上传的文件为空");
            this.uploadFiles.put(file.getUploadKey(), file);
            index++;
        }
    }
}
